package controller

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"strings"
	"zong-microservice/app/models"
	"zong-microservice/pkg/auth"
	"zong-microservice/pkg/cache"
	c "zong-microservice/pkg/config"
	"zong-microservice/pkg/helper"
	"zong-microservice/pkg/logger"
	"zong-microservice/pkg/rabbitmq"
	"zong-microservice/pkg/response"
	"zong-microservice/pkg/storage"
)

type UploadController struct {
}

// Upload 渲染上传页面
func (upload UploadController) Upload(context *gin.Context) {
	context.HTML(http.StatusOK, "upload.html", gin.H{})
}

// DoUpload 处理文件上传
func (upload *UploadController) DoUpload(context *gin.Context) {
	// 单文件
	// 1. 从form表单中获得文件内容句柄
	file, header, err := context.Request.FormFile("file")
	if err != nil {
		response.ResponseError(context, err.Error())
		return
	}
	defer file.Close()

	// 2. 把文件内容转为[]byte
	buf := bytes.NewBuffer(nil)
	if _, err = io.Copy(buf, file); err != nil {
		response.ResponseError(context, err.Error())
		return
	}

	// 3. 构建文件元信息
	fileMeta := models.File{
		FileSha1: helper.Sha1(buf.Bytes()), //　计算文件sha1
		FileName: header.Filename,
		FileSize: int64(len(buf.Bytes())),
	}

	// 4. 将文件写入临时存储位置
	fileMeta.FileAddr = c.Get("storage", "path") + fileMeta.FileSha1 + path.Ext(fileMeta.FileName) // 临时存储地址
	newFile, err := os.Create(fileMeta.FileAddr)
	if err != nil {
		response.ResponseError(context, err.Error())
		return
	}
	defer newFile.Close()

	nByte, err := newFile.Write(buf.Bytes())
	if int64(nByte) != fileMeta.FileSize || err != nil {
		response.ResponseError(context, err.Error())
		return
	}

	// 5. 同步或异步将文件转移到Ceph/OSS
	if c.Get("storage", "store") == "CEPH" {
		// 文件写入Ceph存储
		data, _ := ioutil.ReadAll(newFile)
		cephPath := c.Get("storage", "path") + fileMeta.FileSha1
		_ = storage.PutCephObject("UserFile", cephPath, data)
		fileMeta.FileAddr = cephPath
	} else if c.Get("storage", "store") == "OSS" {
		// 文件写入OSS存储
		ossPath := c.Get("storage", "path") + fileMeta.FileSha1
		// 判断写入OSS为同步还是异步
		if c.Get("storage", "async_transfer") == "false" { // 同步
			// TODO: 设置oss中的文件名，方便指定文件名下载
			err = storage.OssBucket().PutObject(ossPath, newFile)
			if err != nil {
				logger.LogError(err)
				return
			}
			fileMeta.FileAddr = ossPath
		} else { // 异步
			// 写入异步转移任务队列
			data := rabbitmq.TransferData{
				FileHash:      fileMeta.FileSha1,
				CurLocation:   fileMeta.FileAddr,
				DestLocation:  ossPath,
				DestStoreType: storage.StoreOSS,
			}
			pubData, _ := json.Marshal(data)
			pubSuc := rabbitmq.Publish(
				c.Get("rabbitmq", "trans_exchange_name"),
				c.Get("rabbitmq", "trans_routing_key"),
				pubData,
			)
			if !pubSuc {
				// TODO: 当前发送转移信息失败，稍后重试
			}
		}
	}

	// 6. 创建文件表记录
	_, err = fileMeta.GetByFileSha1(fileMeta.FileSha1)
	if err == nil {
		response.ResponseError(context, "文件已存在")
		return
	}
	if err = fileMeta.Create(); err != nil {
		response.ResponseError(context, err.Error())
		return
	}

	// 7. 创建用户文件表
	authorization := context.Request.Header.Get("Authorization")
	token, _ := auth.ParseToken(strings.Fields(authorization)[1])
	if !cache.Exists("UserCache_" + token.Id) {
		response.ResponseError(context, "Token Error.")
		return
	} else {
		userCache := cache.Get("UserCache_" + token.Id)
		userData := fmt.Sprintf("%v", userCache) // interface{} 转 string
		currentUser := &models.ApiUser{}
		// json反序列化
		if err = json.Unmarshal([]byte(userData), currentUser); err != nil {
			response.ResponseError(context, "Token Error.")
			return
		} else {
			userFile := models.UserFile{
				Username: currentUser.Username,
				FileSha1: fileMeta.FileSha1,
				FileName: fileMeta.FileName,
				FileSize: fileMeta.FileSize,
			}
			if err = userFile.Create(); err != nil {
				response.ResponseError(context, err.Error())
				return
			}
		}
	}

	response.ResponseSuccess(context, "success", "Upload Success")
}

// DoFastUpload 处理文件秒传
func (upload *UploadController) DoFastUpload(context *gin.Context) {
	// 1. 从form表单中获得文件内容句柄
	file, _, err := context.Request.FormFile("file")
	if err != nil {
		response.ResponseError(context, err.Error())
		return
	}
	defer file.Close()

	// 2. 把文件内容转为[]byte
	buf := bytes.NewBuffer(nil)
	if _, err = io.Copy(buf, file); err != nil {
		response.ResponseError(context, err.Error())
		return
	}

	// 3. 从文件表中查询相同hash的文件记录
	fileMeta := models.File{}
	fileMetaResp, err := fileMeta.GetByFileSha1(helper.Sha1(buf.Bytes()))
	if err != nil {
		// 查不到记录则返回秒传失败
		response.ResponseError(context, "秒传失败，请访问普通上传接口")
		return
	}

	// 4. 上传过则将文件信息写入用户文件表， 返回成功
	authorization := context.Request.Header.Get("Authorization")
	token, _ := auth.ParseToken(strings.Fields(authorization)[1])
	if !cache.Exists("UserCache_" + token.Id) {
		response.ResponseError(context, "Token Error.")
		return
	} else {
		userCache := cache.Get("UserCache_" + token.Id)
		userData := fmt.Sprintf("%v", userCache) // interface{} 转 string
		currentUser := &models.ApiUser{}
		// json反序列化
		if err = json.Unmarshal([]byte(userData), currentUser); err != nil {
			response.ResponseError(context, "Token Error.")
			return
		} else {
			userFile := models.UserFile{
				Username: currentUser.Username,
				FileSha1: fileMetaResp.FileSha1,
				FileName: fileMetaResp.FileName,
				FileSize: fileMetaResp.FileSize,
			}
			if err = userFile.Create(); err != nil {
				response.ResponseError(context, "秒传失败，请稍后重试")
				return
			}
		}
	}

	response.ResponseSuccess(context, "success", "Fast Upload Success")
}
